package com.zb.controller;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONArray;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.jdom.JDOMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.zb.utils.MD5Util;
import com.zb.utils.SettingsUtil;
import com.zb.utils.TenpayUtil;
import com.zb.utils.WXUtil;
import com.zb.utils.XMLUtil;

/**
 * 微信支付-调用微信浏览器(扫码)自带的对象发起支付
 */
@Controller
@RequestMapping("weixinNATIVE")
public class WeixinNATIVEController {

	private static Logger log = LoggerFactory
			.getLogger(WeixinNATIVEController.class);

	/** 支付密钥，商户平台 > API安全 > 密钥管理 中进行设置 */
	private static final String API_KEY = SettingsUtil.getInstance().getString(
			"wx.apikey");

	/** 支付的回调方法，微信服务器调用 */
	private static final String NOTIFY_URL = "weixinJSBridge/pay";

	/** 获取统一下单接口 预支付单号prepay_id */
	private static final String UNI_URL = SettingsUtil.getInstance().getString(
			"wx.uniurl");

	/** 微信公众号APPID */
	private static final String APPID = SettingsUtil.getInstance().getString(
			"wx.appid");

	/** 微信公众号绑定的商户号 */
	private static final String MCH_ID = SettingsUtil.getInstance().getString(
			"wx.mchid");

	/** 测试微信号的product_id，这里固定写成我的微信product_id，你们到时候自己编码获取 */
	private static final String PRODUCT_ID = "12";

	/***
	 * 调用统一下单接口获取预支付单号prepay_id，生成订单数据 以及 微信支付需要的签名等信息 传输到前端，进行调用JSAPI支付接口
	 * 
	 * 作者: zhoubang 日期：2015年6月10日 上午9:31:42
	 * 
	 * @param commodityName
	 *            商品名称
	 * @param totalPrice
	 *            总金额(元)
	 * @param request
	 * @return
	 */
	@SuppressWarnings("unchecked")
	@RequestMapping(value = "gopay", method = RequestMethod.POST)
	public @ResponseBody String Gopay(HttpServletRequest request,
			HttpServletResponse response, String commodityName,
			double totalPrice) {
		String path = request.getContextPath();
		String basePath = request.getScheme() + "://" + request.getServerName()
				+ ":" + request.getServerPort() + path + "/";
		log.info("basePath=" + basePath);

		/** 总金额(分为单位) */
		int total = (int) (totalPrice * 100);

		SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();

		parameters.put("device_info", "");
		/** 公众号APPID */
		parameters.put("appid", APPID);
		/** 商户号 */
		parameters.put("mch_id", MCH_ID);
		/** 随机字符串 */
		parameters.put("nonce_str", WXUtil.getNonceStr());
		/** 商品名称 */
		parameters.put("body", commodityName);

		parameters.put("fee_type", "CNY");

		/** 当前时间 yyyyMMddHHmmss */
		String currTime = TenpayUtil.getCurrTime();
		/** 8位日期 */
		String strTime = currTime.substring(8, currTime.length());
		/** 四位随机数 */
		String strRandom = TenpayUtil.buildRandom(4) + "";
		/** 订单号 */
		// parameters.put("out_trade_no", strTime + strRandom);
		String out_trade_no = strTime+strRandom;
		System.out.println("out_trade_no------" + out_trade_no);
		parameters.put("out_trade_no", out_trade_no);

		/** 订单金额以分为单位，只能为整数 */
		parameters.put("total_fee", total);
		/** 客户端本地ip */
		parameters.put("spbill_create_ip", request.getRemoteAddr());
		/** 支付回调地址 */
		parameters.put("notify_url", basePath + NOTIFY_URL);
		/** 支付方式为JSAPI支付 */
		parameters.put("trade_type", "NATIVE");
		/** 用户微信的，当trade_type为NATIVE的时候，该属性字段必须设置 */
		parameters.put("product_id", PRODUCT_ID);

		/** MD5进行签名，必须为UTF-8编码，注意上面几个参数名称的大小写 */
		String sign = createSign("UTF-8", parameters);
		parameters.put("sign", sign);
		System.out.println("sign--------" + sign);

		/** 生成xml结构的数据，用于统一下单接口的请求 */
		String requestXML = getRequestXml(parameters);
		log.info("requestXML：" + requestXML);
		/** 开始请求统一下单接口，获取预支付prepay_id */
		HttpClient client = new HttpClient();
		PostMethod myPost = new PostMethod(UNI_URL);
		client.getParams().setSoTimeout(300 * 1000);
		String result = null;
		try {
			myPost.setRequestEntity(new StringRequestEntity(requestXML,
					"text/xml", "utf-8"));
			int statusCode = client.executeMethod(myPost);
			if (statusCode == HttpStatus.SC_OK) {
				BufferedInputStream bis = new BufferedInputStream(
						myPost.getResponseBodyAsStream());
				byte[] bytes = new byte[1024];
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				int count = 0;
				while ((count = bis.read(bytes)) != -1) {
					bos.write(bytes, 0, count);
				}
				byte[] strByte = bos.toByteArray();
				result = new String(strByte, 0, strByte.length, "utf-8");
				bos.close();
				bis.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		/** 需要释放掉、关闭连接 */
		myPost.releaseConnection();
		client.getHttpConnectionManager().closeIdleConnections(0);

		log.info("请求统一支付接口的返回结果:");
		log.info(result);
		try {
			/** 解析微信返回的信息，以Map形式存储便于取值 */
			Map<String, String> map = XMLUtil.doXMLParse(result);

			SortedMap<Object, Object> params = new TreeMap<Object, Object>();

			params.put("appId", APPID);
			params.put("timeStamp", "\"" + new Date().getTime() + "\"");
			params.put("nonceStr", WXUtil.getNonceStr());
			/**
			 * 获取预支付单号prepay_id后，需要将它参与签名。
			 * 微信支付最新接口中，要求package的值的固定格式为prepay_id=...
			 */
			params.put("package", "prepay_id=" + map.get("prepay_id"));

			String code_url = map.get("code_url");
			System.out.println("code_url-----" + code_url);
			params.put("code_url", code_url);
			/** 微信支付新版本签名算法使用MD5，不是SHA1 */
			params.put("signType", "MD5");
			/**
			 * 获取预支付prepay_id之后，需要再次进行签名，参与签名的参数有：appId、timeStamp、nonceStr、
			 * package、signType. 主意上面参数名称的大小写.
			 * 该签名用于前端js中WeixinJSBridge.invoke中的paySign的参数值
			 */
			String paySign = createSign("UTF-8", params);
			params.put("paySign", paySign);

			/** 预支付单号，前端ajax回调获取。由于js中package为关键字，所以，这里使用packageValue作为key。 */
			params.put("packageValue", "prepay_id=" + map.get("prepay_id"));

			/** 付款成功后，微信会同步请求我们自定义的成功通知页面，通知用户支付成功 */
			params.put("sendUrl", basePath + "pay/paysuccess?totalPrice="
					+ totalPrice);
			/** 获取用户的微信客户端版本号，用于前端支付之前进行版本判断，微信版本低于5.0无法使用微信支付 */
			String userAgent = request.getHeader("user-agent");
			char agent = userAgent
					.charAt(userAgent.indexOf("MicroMessenger") + 15);
			// params.put("agent", new String(new char[] { agent }));
			params.put("agent", 6);

			params.put("out_trade_no", out_trade_no);

			String json = JSONArray.fromObject(params).toString();

			System.out.println(json);

			return json;

		} catch (JDOMException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "";
	}

	/**
	 * sign签名
	 * 
	 * 作者: zhoubang 日期：2015年6月10日 上午9:31:24
	 * 
	 * @param characterEncoding
	 * @param parameters
	 * @return
	 */
	public static String createSign(String characterEncoding,
			SortedMap<Object, Object> parameters) {
		StringBuffer sb = new StringBuffer();
		Set<Entry<Object, Object>> es = parameters.entrySet();
		Iterator<Entry<Object, Object>> it = es.iterator();
		while (it.hasNext()) {
			Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) it
					.next();
			String k = (String) entry.getKey();
			Object v = entry.getValue();
			/** 如果参数为key或者sign，则不参与加密签名 */
			if (null != v && !"".equals(v) && !"sign".equals(k)
					&& !"key".equals(k)) {
				sb.append(k + "=" + v + "&");
			}
		}
		/** 支付密钥必须参与加密，放在字符串最后面 */
		sb.append("key=" + API_KEY);
		/** 记得最后一定要转换为大写 */
		String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding)
				.toUpperCase();
		return sign;
	}

	/**
	 * 将请求参数转换为xml格式的string
	 * 
	 * 作者: zhoubang 日期：2015年6月10日 上午9:25:51
	 * 
	 * @param parameters
	 * @return
	 */
	public static String getRequestXml(SortedMap<Object, Object> parameters) {
		StringBuffer sb = new StringBuffer();
		sb.append("<xml>");
		Set<Entry<Object, Object>> es = parameters.entrySet();
		Iterator<Entry<Object, Object>> it = es.iterator();
		while (it.hasNext()) {
			Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) it
					.next();
			String k = (String) entry.getKey();
			String v = entry.getValue() + "";
			if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k)
					|| "sign".equalsIgnoreCase(k)) {
				sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
			} else {
				sb.append("<" + k + ">" + v + "</" + k + ">");
			}
		}
		sb.append("</xml>");
		return sb.toString();
	}

	/***
	 * 付款成功回调处理
	 * 
	 * 作者: zhoubang 日期：2015年6月10日 上午9:25:29
	 * 
	 * @param request
	 * @param response
	 * @throws IOException
	 * @throws JDOMException
	 */
	@SuppressWarnings("unchecked")
	@RequestMapping(value = "pay")
	public void notify_success(HttpServletRequest request,
			HttpServletResponse response) throws IOException, JDOMException {
		log.info("微信支付成功调用回调URL");
		InputStream inStream = request.getInputStream();
		ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len = 0;
		while ((len = inStream.read(buffer)) != -1) {
			outSteam.write(buffer, 0, len);
		}
		log.info("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");
		outSteam.close();
		inStream.close();

		/** 支付成功后，微信回调返回的信息 */
		String result = new String(outSteam.toByteArray(), "utf-8");
		log.info("微信返回的订单支付信息:" + result);
		Map<Object, Object> map = XMLUtil.doXMLParse(result);

		// 用于验签
		SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
		for (Object keyValue : map.keySet()) {
			/** 输出返回的订单支付信息 */
			log.info(keyValue + "=" + map.get(keyValue));
			if (!"sign".equals(keyValue)) {
				parameters.put(keyValue, map.get(keyValue));
			}
		}
		if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
			// 先进行校验，是否是微信服务器返回的信息
			String checkSign = createSign("UTF-8", parameters);
			log.info("对服务器返回的结果进行签名：" + checkSign);
			log.info("服务器返回的结果签名：" + map.get("sign"));
			if (checkSign.equals(map.get("sign"))) {// 如果签名和服务器返回的签名一致，说明数据没有被篡改过
				log.info("签名校验成功，信息合法，未被篡改过");

				// 告诉微信服务器，我收到信息了，不要再调用回调方法了

				/**
				 * 如果不返回SUCCESS的信息给微信服务器，则微信服务器会在一定时间内，多次调用该回调方法，如果最终还未收到回馈，
				 * 微信默认该订单支付失败
				 */
				/** 微信默认会调用8次该回调地址 */
				/*
				 * 【需要注意】： 后期很多朋友都反应说，微信会一直请求这个回调地址，也都是使用的是
				 * response.getWriter().write(setXML("SUCCESS", ""));
				 * 百思不得其解，最终发现处理办法。其实不能直接使用response.getWriter()返回结果，这样微信是接收不到的。
				 * 只能使用OutputStream流的方式返回结果给微信。 切记！！！！
				 */
				OutputStream outputStream = null;
				try {
					outputStream = response.getOutputStream();
					outputStream.flush();
					outputStream.write(setXML("SUCCESS", "").getBytes());
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					try {
						outputStream.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}

				response.getWriter().write(setXML("SUCCESS", ""));

				log.info("-------------" + setXML("SUCCESS", ""));
			}
		}
	}

	/**
	 * 发送xml格式数据到微信服务器 告知微信服务器回调信息已经收到。
	 * 
	 * 作者: zhoubang 日期：2015年6月10日 上午9:27:33
	 * 
	 * @param return_code
	 * @param return_msg
	 * @return
	 */
	public static String setXML(String return_code, String return_msg) {
		return "<xml><return_code><![CDATA[" + return_code
				+ "]]></return_code><return_msg><![CDATA[" + return_msg
				+ "]]></return_msg></xml>";
	}

	/**
	 * 支付成功请求的地址URL，告知用户已经支付成功
	 * 
	 * 作者: zhoubang 日期：2015年6月10日 上午10:37:35
	 * 
	 * @param request
	 * @param response
	 * @param totalPrice
	 *            金额单位为元
	 * @return
	 */
	@RequestMapping("paysuccess")
	public ModelAndView paysuccess(HttpServletRequest request,
			HttpServletResponse response, String code_url, String out_trade_no) {
		ModelAndView mav = new ModelAndView("forward:" + "/weixinpay.jsp");
		mav.addObject("code_url", code_url);
		mav.addObject("out_trade_no", out_trade_no);
		return mav;
	}

	@RequestMapping("orderQuery")
	public @ResponseBody String orderQuery(HttpServletRequest request,
			HttpServletResponse response, String out_trade_no) {
		SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
		parameters.put("appid", APPID); // appid
		parameters.put("mch_id", MCH_ID); // 微信商户号
		parameters.put("nonce_str", WXUtil.getNonceStr()); // 随机字符串

		/**
		 * 这里有4种选择，依次是： transaction_id：微信订单号 out_trade_no：商户订单号，商户系统内部的订单号
		 * 
		 * 推荐优先使用transaction_id，只要填写其中一个作为参数即可。至于值如何获取，我想，在你们支付之后，这些订单信息已经有了。
		 */
		parameters.put("out_trade_no", out_trade_no); // 这里使用微信订单号

		// 创建签名，算法与支付的算法一样
		String sign = createSign("UTF-8", parameters);
		parameters.put("sign", sign);

		// 生成请求报文
		String requestXML = getRequestXml(parameters);

		HttpClient client = new HttpClient();
		PostMethod myPost = new PostMethod(
				"https://api.mch.weixin.qq.com/pay/orderquery");// 订单查询接口
		client.getParams().setSoTimeout(300 * 1000);
		String result = null;
		try {
			myPost.setRequestEntity(new StringRequestEntity(requestXML,
					"text/xml", "utf-8"));
			int statusCode = client.executeMethod(myPost);
			if (statusCode == HttpStatus.SC_OK) {
				BufferedInputStream bis = new BufferedInputStream(
						myPost.getResponseBodyAsStream());
				byte[] bytes = new byte[1024];
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				int count = 0;
				while ((count = bis.read(bytes)) != -1) {
					bos.write(bytes, 0, count);
				}
				byte[] strByte = bos.toByteArray();
				result = new String(strByte, 0, strByte.length, "utf-8");
				bos.close();
				bis.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		// 输出微信返回的订单信息详情
		System.out.println(result);

		Map<Object, Object> map = null;
		try {
			map = XMLUtil.doXMLParse(result);
		} catch (JDOMException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		System.out.println(map);
		// 用于验签
		SortedMap<Object, Object> parameters2 = new TreeMap<Object, Object>();
		for (Object keyValue : map.keySet()) {
			/** 输出返回的订单支付信息 */
			log.info(keyValue + "=" + map.get(keyValue));
			if (!"sign".equals(keyValue)) {
				parameters2.put(keyValue, map.get(keyValue));
			}
		}
		if (map.get("trade_state").toString().equalsIgnoreCase("SUCCESS")) {
			// 下面可以处理其他的业务...
			SortedMap<Object, Object> params = new TreeMap<Object, Object>();
			params.put("data", 1);
			return "1";
		}
		return "0";
	}
}
